Skip to content

Fix GH-23232: is_callable('\::method') asks the autoloader for an empty class name - #23233

Open
spawnia wants to merge 1 commit into
php:PHP-8.4from
spawnia:fix/is-callable-empty-class-name
Open

Fix GH-23232: is_callable('\::method') asks the autoloader for an empty class name#23233
spawnia wants to merge 1 commit into
php:PHP-8.4from
spawnia:fix/is-callable-empty-class-name

Conversation

@spawnia

@spawnia spawnia commented Aug 12, 2026

Copy link
Copy Markdown

Fixes GH-23232.

zend_lookup_class_ex() rejects an empty class name, but not a name consisting solely of the namespace separator: "\" passes the length check, gets its leading \ stripped, and is then looked up — and autoloaded — as "".

That is reachable from userland: is_callable('\::method') splits at ::, takes "\" as the class part and hands it to zend_lookup_class(). is_callable('::method') is rejected as an invalid function name earlier, which is where the asymmetry in the issue comes from.

A lone \ names no class, so return NULL before consulting the class table or the autoloader.

This is observable, not just wasted work: Composer's ClassLoader::findFileWithExtension() does $first = $class[0]; and warns Uninitialized string offset 0 when handed '', which in applications that promote warnings to exceptions aborts the request. We hit it in CI through Laravel's Factory::expandAttributes(), which calls is_callable() on every string attribute — a randomly generated password starting with \:: was enough.

Targeting PHP-8.4 as the lowest branch still receiving bug fixes.

make test passes on Zend/tests and ext/standard/tests/general_functions (5085 passed, 0 failed) in a debug build; the new .phpt fails without the patch.

…empty class name

A class name consisting solely of the namespace separator passed the
length check in zend_lookup_class_ex(), lost its leading backslash and
was then looked up and autoloaded as an empty string.
@spawnia
spawnia changed the base branch from master to PHP-8.4 August 12, 2026 15:16

@Girgias Girgias left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems okay as a fix, might be worse to see if this can be prevent on the call sites for the future.

@spawnia
spawnia marked this pull request as ready for review August 13, 2026 06:29
@spawnia
spawnia requested a review from dstogov as a code owner August 13, 2026 06:29
@spawnia

spawnia commented Aug 13, 2026

Copy link
Copy Markdown
Author

Thanks! I put the guard in zend_lookup_class_ex() deliberately: every path that can produce a lone \is_callable(), class_exists(), string-callable invocation, callable type coercion — funnels through it, so one check covers them all and no future call site can regress.

Validating at the call sites would mean duplicating "is this even a syntactically possible class name?" in each of them. zend_is_callable_check_class() is the main offender today: it hands over whatever precedes :: without inspecting it. Happy to follow up with a stricter check there (and in the other string-callable parsers) as a separate PR if you'd prefer that on top — it would turn the autoloader call into a no-op earlier, but it is a behaviour question rather than a bug fix.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

is_callable('\::method') asks the autoloader for an empty class name

2 participants